﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Windows.Forms;
using MMOEng;
using MMOShared;
using MMOShared.Packets;
using System.Threading;

namespace MMOConnector
{
    public partial class FileDownloadForm : Form
    {
        private TcpClient client;
        private NetworkStream stream;
        private BinaryFormatter bf;
        private GameConfiguration gc;
        private VersionsAvailable versions;

        public delegate void VersionUpdated(UInt16 version);
        public event VersionUpdated OnVersionUpdated;

        public delegate void SetTextCallback(string text);
        public delegate void SetVisibleCallback();
        public delegate void SetValueCallback(int value);
        public delegate void SetEnabledCallback(bool value);

        public FileDownloadForm(GameConfiguration gc, TcpClient client, VersionsAvailable versions)
        {
            InitializeComponent();

            this.client = client;
            this.stream = client.GetStream();
            this.bf = new BinaryFormatter();
            this.gc = gc;
            this.versions = versions;

            VersionsListReceivedUpdateLabels();

            UpdateDownloader updDL = new UpdateDownloader(gc, client, versions);
            updDL.OnFileDownloaded += new UpdateDownloader.FileDownloaded(updDL_OnFileDownloaded);
            updDL.OnHeaderReceived += new UpdateDownloader.HeaderReceived(UpdateHeaderReceivedUpdateLabels);
            updDL.OnPieceDownloaded += new UpdateDownloader.PieceDownloaded(updDL_OnPieceDownloaded);
            updDL.OnStatusLabeled += new UpdateDownloader.StatusLabled(updDL_OnStatusLabeled);
            updDL.OnStatusUpdated += new UpdateDownloader.StatusUpdated(updDL_OnStatusUpdated);
            updDL.OnDownloadFinished += new UpdateDownloader.DownloadFinished(updDL_OnDownloadFinished);
            updDL.OnVersionFinished += new UpdateDownloader.VersionFinished(updDL_OnVersionFinished);

            Thread t = new Thread(() => updDL.DownloadAllUpdates());
            //Admit new connection in new thread
            t.Start();
            
        }

        private void updDL_OnVersionFinished(string status)
        {
            if (this.statusL.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText_currVersionL);
                this.Invoke
                    (d, new object[] { status });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.currVersionL.Text = status;
            }
        }

        private void SetEnabled_doneB(bool value)
        {
            this.doneB.Enabled = value;
        }

        private void SetEnabled_cancelB(bool value)
        {
            this.cancelB.Enabled = value;
        }

        void updDL_OnDownloadFinished()
        {
            if (this.doneB.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetEnabledCallback d = new SetEnabledCallback(SetEnabled_doneB);
                this.Invoke
                    (d, new object[] { true });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.doneB.Enabled = true;
            }
            if (this.cancelB.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetEnabledCallback d = new SetEnabledCallback(SetEnabled_cancelB);
                this.Invoke
                    (d, new object[] { false });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.cancelB.Enabled = false;
            }
            //Update the version
            OnVersionUpdated(versions.LatestVersion);
        }

        private void SetText_statusL(string status)
        {
            this.statusL.Text = status;
        }

        void updDL_OnStatusUpdated(string status)
        {
            if (this.statusL.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText_statusL);
                this.Invoke
                    (d, new object[] { status});
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.statusL.Text = status;
            }
        }

        private void SetVisible_currFileL()
        {
            this.currFileL.Visible = true;
        }

        void updDL_OnStatusLabeled()
        {
            if (this.currFileL.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetVisibleCallback d = new SetVisibleCallback(SetVisible_currFileL);
                this.Invoke
                    (d,null);
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.currFileL.Visible = true;
            }
        }

        void SetValue_overallP(int value)
        {
            this.overallP.Value = value;
        }
        void updDL_OnPieceDownloaded(string file, int piece)
        {
            if (this.statusL.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText_statusL);
                this.Invoke
                    (d, new object[] { file });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.statusL.Text = file;
            }
            if (this.overallP.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetValueCallback d = new SetValueCallback(SetValue_overallP);
                this.Invoke
                    (d, new object[] { piece });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.overallP.Value = piece;
            }
        }

        private void SetText_currFileNumL(string text)
        {
            this.currFileNumL.Text = text;
        }
        void updDL_OnFileDownloaded(string status)
        {
            if (this.currFileNumL.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText_currFileNumL);
                this.Invoke
                    (d, new object[] { status });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.currFileNumL.Text = status;
            }
        }

        private void SetValue_overallPInit(int value)
        {
            this.overallP.Minimum = 0;
            this.overallP.Maximum = value;
            this.overallP.Step = 1;
            this.overallP.Value = 0;
        }

        private void UpdateHeaderReceivedUpdateLabels(UpdateHeader head)
        {
            if (this.statusL.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText_statusL);
                this.Invoke
                    (d, new object[] { "Waiting for files to begin transferring..." });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.statusL.Text = "Waiting for files to begin transferring...";
            }
            if (this.overallP.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetValueCallback d = new SetValueCallback(SetValue_overallPInit);
                this.Invoke
                    (d, new object[] { head.NumberOfPieces });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.overallP.Value = head.NumberOfPieces;
            }
            if (this.currFileNumL.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText_currFileNumL);
                this.Invoke
                    (d, new object[] { "1 / " + head.NumberOfFiles });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.currFileNumL.Text = "1 / " + head.NumberOfFiles;
            }
            if (this.sizeL.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText_sizeL);
                this.Invoke
                    (d, new object[] { "Update Size: " + (head.TotalSize / 1000) + " kb" });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.sizeL.Text = "Update Size: " + (head.TotalSize / 1000) + " kb";
            }
        }
        private void SetText_sizeL(string text)
        {
            this.sizeL.Text = text;
        }
        private void SetText_currVersionL(string text)
        {
            this.currVersionL.Text = text;
        }

        private void VersionsListReceivedUpdateLabels()
        {
            if (this.statusL.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText_statusL);
                this.Invoke
                    (d, new object[] { "Received list of available updates..." });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.statusL.Text = "Received list of available updates...";
            }
            if (this.currVersionL.InvokeRequired)
            {
                // It's on a different thread, so use Invoke.
                SetTextCallback d = new SetTextCallback(SetText_currVersionL);
                this.Invoke
                    (d, new object[] { "Downloading Update " + (gc.GameVersion + 1) + " of " + versions.LatestVersion });
            }
            else
            {
                // It's on the same thread, no need for Invoke
                this.currVersionL.Text = "Downloading Update " + (gc.GameVersion + 1) + " of " + versions.LatestVersion;
            }
        }

        private void doneB_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    }
}
